using System;
using System.ComponentModel;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Content.Pipeline;
using Microsoft.Xna.Framework.Content.Pipeline.Graphics;

namespace BlobSim.ContentPipelineExtension
{
    [ContentProcessor]
    public class CubemapProcessor : ContentProcessor<TextureContent, TextureCubeContent>
    {
        #region Private Data

        private int _faceSize = 256;
        private bool _generateMipMaps = true;

        #endregion

        #region Parameters

        [DisplayName("Face Size")]
        [DefaultValue(256)]
        [Description("Size of a cube texture's face.")]
        public int FaceSize
        {
            get { return _faceSize; }
            set { _faceSize = value; }
        }

        [DisplayName("Generate MipMaps")]
        [DefaultValue(true)]
        [Description("Generate mipmaps for the cube texture's faces.")]
        public bool GenerateMipMaps
        {
            get { return _generateMipMaps; }
            set { _generateMipMaps = value; }
        }

        #endregion
        
        public override TextureCubeContent Process(TextureContent input,
                                                   ContentProcessorContext context)
        {
            input.ConvertBitmapType(typeof(PixelBitmapContent<Color>));
            var image = (PixelBitmapContent<Color>) input.Faces[0][0];

            if (image.Width != 4 * _faceSize ||
                image.Height != 3 * _faceSize)
                throw new ArgumentException("wrong image size");

            var cubemap = new TextureCubeContent();

            cubemap.Faces[(int)CubeMapFace.NegativeX] = ExtractCubemapFace(image, _faceSize, 2, 1, -1, true, false);
            cubemap.Faces[(int)CubeMapFace.PositiveX] = ExtractCubemapFace(image, _faceSize, 0, 1, -1, true, false);
            cubemap.Faces[(int)CubeMapFace.NegativeY] = ExtractCubemapFace(image, _faceSize, 1, 2, 1, true, false);
            cubemap.Faces[(int)CubeMapFace.PositiveY] = ExtractCubemapFace(image, _faceSize, 1, 0, 1, true, false);
            cubemap.Faces[(int)CubeMapFace.NegativeZ] = ExtractCubemapFace(image, _faceSize, 1, 1, -1, true, false);
            cubemap.Faces[(int)CubeMapFace.PositiveZ] = ExtractCubemapFace(image, _faceSize, 1, 3, 1, true, false);

            if (_generateMipMaps)
            {
                cubemap.GenerateMipmaps(true);
            }

            cubemap.ConvertBitmapType(typeof(Dxt1BitmapContent));

            return cubemap;
        }

        static PixelBitmapContent<Color> ExtractCubemapFace(
            PixelBitmapContent<Color> source,
            int faceSize,
            int rowIdx, int colIdx,
            int ccwRotations,
            bool flipHorizontal,
            bool flipVertical)
        {
            var face = new PixelBitmapContent<Color>(faceSize, faceSize);

            for (int y = 0; y < faceSize; y++)
            {
                for (int x = 0; x < faceSize; x++)
                {
                    var color = source.GetPixel(x + faceSize * colIdx, y + faceSize * rowIdx);

                    var xx = x;
                    var yy = y;

                    if (ccwRotations != 0)
                    {
                        GetIndicesRotated(faceSize, ref xx, ref yy, ccwRotations);
                    }

                    if (flipHorizontal)
                    {
                        GetFlippedIndex(faceSize, ref xx);
                    }

                    if (flipVertical)
                    {
                        GetFlippedIndex(faceSize, ref yy);
                    }

                    face.SetPixel(xx, yy, color);
                }
            }
            return face;
        }

        private static void GetIndicesRotated(int size, ref int xx, ref int yy, int ccwRotations)
        {
            if (ccwRotations == 0)
                return;

            while (ccwRotations > 0)
            {
                GetIndicesRotatedCCW(size, ref xx, ref yy);
                ccwRotations--;
            }

            while (ccwRotations < 0)
            {
                GetIndicesRotatedCW(size, ref xx, ref yy);
                ccwRotations++;
            }
        }

        private static void GetIndicesRotatedCCW(int size, ref int x, ref int y)
        {
            var tmpX = x;
            x = y;
            y = (size - 1) - tmpX;
        }

        private static void GetIndicesRotatedCW(int size, ref int x, ref int y)
        {
            var tmpX = x;
            x = (size - 1) - y;
            y = tmpX;
        }

        private static void GetFlippedIndex(int size, ref int idx)
        {
            idx = (size - 1) - idx;
        }
    }
}
